/*
    Contact info:
    bhepple@freeshell.org
    http://bhepple.freeshell.org/scrmgr

    Copyright (C) 1991-2008 Bob Hepple

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; see the file COPYING.  If not, write to
    the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
    Boston, MA 02111-1307, USA.

*/
/* smdb.c ... all the links to the data store are here */
/* Presently only supports Memory & file storage. dbm storage to follow */

/* MS-DOG under TurboC 2.0: fgets DOES put \n into the buffer!!!! */

#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
#define SM_CLASS extern
#include "smlib.h"
#include "backup.h"

/* To create a standalone test program on SunOS, compile with:
gcc -g -o s -DTEST=1 -DSM_DEBUG -DSM_SUNOS -I/usr/local/include -L/usr/local/lib -target sun4 smdb.c smstubio.o libsm.a -lmylib

On Linux:

gcc -g -o s -DTEST=1 -DSM_DEBUG -DSM_SYSV  -I/usr/local/include -L/usr/local/lib smdb.c smstubio.o libsm.a -lmylib 

Use the test script smdb.test to verify operation of FILE code
(assumes that MEMORY code is OK - actually, it assumes that one or the
other is OK!)

screen->index refers to the record being looked at by the client routines
screen->{upper,lower}->index refers to the record position of the temporary file.
Thus 0 is _before_ the first record, 1 is after the first record and before the
second.

*/

CHAR *smdb_rcsid(void) {
    return("$Id: smdb.c,v 1.7 2008/02/19 13:15:50 bhepple Exp $");
}
    
/* Note that LOWER_/UPPER_INDEX have the same base as SCREEN_INDEX */
#define DATA 		screen->data
#define SCREEN_INDEX	screen->index		/* Current record number 	*/
#define LOWER		screen->lower		/* FILE* 			*/
#define UPPER 		screen->upper		/* FILE* 			*/
#define LOWER_INDEX 	screen->lower_index	/* Record position in LOWER 	*/
#define UPPER_INDEX	screen->upper_index	/* Record position in UPPER 	*/
#define SIZEOFLOWER	screen->sizeOfLower	/* num records in LOWER 	*/
#define UPPER_ORIGIN	screen->upper_origin	/* first valid record in UPPER 	*/
#define BUF		screen->buf	
#define LOWER_FNAME	screen->lower_fname
#define UPPER_FNAME	screen->upper_fname
#define NUM_RECORDS	screen->num_records
#define UPPER_EXISTS	(screen->sizeOfLower < screen->num_records)

#define CHECKWRITE(x) if ((x)==EOF){smdb_file_error();return(SM_ERROR);}

#ifdef TEST
int 	debugLevel = 0; /* 0=commands & errors only; 
			   1=results of get etc; 
			   2=major file movements; 
			   3=the lot */
int	signalReceived = 0;
FILE 	*trace = NULL;

#define PRINTF0(a) {printf(a);if(trace && trace!=stdout)fprintf(trace,a);}
#define PRINTF1(a,b) {printf(a,b);if(trace && trace!=stdout)fprintf(trace,a,b);}
#define PRINTF2(a,b,c) {printf(a,b,c);if(trace && trace!=stdout)fprintf(trace,a,b,c);}
#define PRINTF3(a,b,c,d) {printf(a,b,c,d);if(trace && trace!=stdout)fprintf(trace,a,b,c,d);}
#define PRINTF4(a,b,c,d,e) {printf(a,b,c,d,e);if(trace && trace!=stdout)fprintf(trace,a,b,c,d,e);}
#define PRINTF5(a,b,c,d,e,f) {printf(a,b,c,d,e,f);if(trace && trace!=stdout)fprintf(trace,a,b,c,d,e,f);}
#endif

static void smdb_file_error()
{
    sm_file_error("Error writing file", "", errno);
}

static void zap_nl ARG1(CHAR *, buf)
{
    CHAR	*p;

    assert(buf);
    if ((p = strchr(buf, '\n')))
	*p = 0;
}

/* Remove control characters _and_ optionally expand tabs */
void smdb_remove_funnies ARG3(CHAR *, out, CHAR *, in, int, untabify)
{
    CHAR	*start = out;

    assert(out && in);

    while (*in)
	if (*in == '\t') {
	    if (untabify) {
		int i = 8 - (out - start) % 8;
	    
		while (i--)
		    *out++ = ' ';
		in++;
	    } else
		*out++ = *in++;
	} else if (*in == '\010') { /* backspace */
	    if (out > start)
		out--;
	    in++;
	} else if (((*in >= ' ') && (*in <= '~')) ||
            (((unsigned char)*in >= 0xa0) && ((unsigned char)*in <= 0xfe)))
	    *out++ = *in++;
	else
	    in++;
    *out = 0;
}

static SM_TEMP_FILE newTempFile()
{
    SM_TEMP_FILE tempFile = (SM_TEMP_FILE) xmalloc(sizeof(struct sm_temp_file_s));
 
    tempFile->file = NULL;
    tempFile->index = tempFile->offset = tempFile->size = 0;
    tempFile->fileName = NULL;
    tempFile->currentRecord = (CHAR *) xmalloc(SM_MAXREC);
    *(tempFile->currentRecord) = 0;
    return(tempFile);
}

void deleteTempFile ARG1(SM_TEMP_FILE, f)
{
    if (f) {
	if (f->file)
	    fclose(f->file);
	if (f->fileName) {
	    unlink(f->fileName);
	    free(f->fileName);
	}
	if (f->currentRecord)
	    free(f->currentRecord);
	free(f);
    }
}

/* Assumes right file is picked and it's in the right position */
static int phyWriteTemp ARG3(SM_SCREEN, screen, SM_TEMP_FILE, f, CHAR *, buf)
{
    int		retval = EOF;

#ifdef TEST
    if (debugLevel > 1)
	PRINTF2("phyWriteTemp on %s, '%s'\n",
		(f==UPPER)?"UPPER":(f==LOWER)?"LOWER":"?",
		buf);
    if (debugLevel > 2)
	dumpScreenParams(screen);
#endif

    if (f && buf) {
	fseek(f->file, ftell(f->file), 0); /* allow write on file */
	retval = fputs(buf, f->file);
	if (retval != EOF)
	    retval = fputc('\n', f->file);
	if (retval != EOF) {
	    f->index++;
	    f->size++;
	    strcpy(f->currentRecord, buf);
	}
	fseek(f->file, ftell(f->file), 0); /* allow read on file */
    }
    return(retval==EOF?SM_ERROR:0);
}

/* Assumes right file is picked and it's in the right position */
static int phyReadTemp ARG2(SM_SCREEN, screen, SM_TEMP_FILE, f)
{
    int		retval;

    *f->currentRecord = 0;
    if (fgets(f->currentRecord, SM_MAXREC, f->file)) {
	zap_nl(f->currentRecord);
	f->index++;
	retval = 0;
    } else
	retval = SM_ERROR;

#ifdef TEST
    if (debugLevel > 1)
	PRINTF3("phyReadTemp returns %d on %s, '%s'\n",
		retval,
		(f==UPPER)?"UPPER":(f==LOWER)?"LOWER":"?",
		f->currentRecord);
    if (debugLevel > 2)
	dumpScreenParams(screen);
#endif

    return(retval);
}
    
/* Assumes that file is pointed just after the record separator */
static int phyReadTempBack ARG2(SM_SCREEN, screen, SM_TEMP_FILE, f)
{
    long	ptr = ftell(f->file);
    CHAR	*buf = alloca(10);

#ifdef TEST
    if (debugLevel > 1)
	PRINTF5("phyReadTempBack on %s, presently at %ld (ftell=%ld) offset = %ld size = %ld\n",
		(f==UPPER)?"UPPER":(f==LOWER)?"LOWER":"?",
		f->index,
		ptr,
		f->offset,
		f->size);
    if (debugLevel > 2)
	dumpScreenParams(screen);
#endif

    if (f->index && ptr) {
	f->index--;
	/* skip previous \n or \r\n */
	if (ptr)
	    ptr--;
#ifdef SM_TURBO
	if (ptr)
	    ptr--;
#endif
	/* Now read backwards till we find a separator */
	while (ptr &&
	       (fseek(f->file, --ptr, 0) == 0) &&
	       fread(buf, 1, 1, f->file) &&
	       (*buf != '\n') && (*buf != '\r'))
	    ;
#if SM_TURBOC
	if (ptr)
	    ptr--; /* for \r */
#endif
	if (ptr)
	    ptr++;
	fseek(f->file, ptr, 0);
	return(0);
    } else
	return(SM_ERROR);
}

/* Put file _before_ record recordNumber ready for read or write.
   Note that f->index is wrong until the subsequent read/write happens.
   Thus this fuction _must_ be followed by read or write */
static int positionTemp ARG3(SM_SCREEN, screen, SM_TEMP_FILE, f, INDEX, recordNumber)
{
    assert(f && screen);

#ifdef TEST
    if (debugLevel > 1)
	PRINTF4("positionTemp to %ld on %s, presently at %ld (ftell=%ld)\n",
		recordNumber,
		(f==UPPER)?"UPPER":(f==LOWER)?"LOWER":"?",
		f->index,
		ftell(f->file));
    if (debugLevel > 2)
	dumpScreenParams(screen);
#endif

    if (recordNumber && f->size) {
	if (f->index != recordNumber) {
	    if (f->index > recordNumber) {
		/* Assume we can read back as fast as forward */
		if (f->index - recordNumber > f->index / 2) {
#ifdef TEST
		    if (debugLevel > 1)
			PRINTF0("Seeking to 0\n");
#endif
		    fseek(f->file, (long)0, 0);
		    f->index = 0;
		} else {
#ifdef TEST
		    if (debugLevel > 1)
			PRINTF0("Reading backwards\n");
#endif
		    while (f->index > recordNumber)
			if (phyReadTempBack(screen, f))
			    return(SM_ERROR);
		}
	    }
	    while (f->index < recordNumber) {
		if (phyReadTemp(screen, f))
		    return(SM_ERROR);
	    }
	}
    } else {
	fseek(f->file, (long) 0, 0);
	f->index = 0;
    }

    return(0);
}

/* Get SCREEN_INDEX'th record */
static CHAR *readRecordFromTemp ARG1(SM_SCREEN, screen)
{
    SM_TEMP_FILE	f;
    INDEX		target;


#ifdef TEST
    if (debugLevel > 1)
	PRINTF1("readRecordFromTemp on %ld\n", SCREEN_INDEX);
    if (debugLevel > 2)
	dumpScreenParams(screen);
#endif

    if (SCREEN_INDEX >= NUM_RECORDS)
	return(NULL);

    if (SCREEN_INDEX < LOWER->size - LOWER->offset) {
	target = SCREEN_INDEX + LOWER->offset;
	f = LOWER;
    } else {
	f = UPPER;
	target = SCREEN_INDEX + UPPER->offset - (LOWER->size - LOWER->offset);
    }

    if (f->size == 0)
	return(NULL);

    if (f->index == target + 1)
	; /* Nothing to do - file is already in position & record has been read in */
    else {
	/* Get ready to read in the right record */
	if (f->index != target)
	    if (positionTemp(screen, f, target))
		return(NULL);
	if (phyReadTemp(screen, f))
	    return(NULL);
    }
    return(f->currentRecord);
}

/* Arrange the split so that LOWER has exactly SCREEN_INDEX records, and is positioned
   to accept the SCREEN_INDEX'th record */
static int positionSplit ARG1(SM_SCREEN, screen)
{
    SM_TEMP_FILE	temp;

#ifdef TEST
    if (debugLevel > 1)
	PRINTF1("positionSplit to %ld\n", SCREEN_INDEX);
    if (debugLevel > 2)
	dumpScreenParams(screen);
#endif

    if (SCREEN_INDEX < LOWER->size - LOWER->offset) {
#ifdef TEST
	if (debugLevel > 1)
	    PRINTF0("SCREEN_INDEX is in LOWER.\n");
#endif
	if (UPPER->size - UPPER->offset) {
#ifdef TEST
	    if (debugLevel > 1)
		PRINTF0("Copy UPPER to LOWER & then swap\n");
#endif
	    if (positionTemp(screen, UPPER, UPPER->offset))
		return(SM_ERROR);
	    if (positionTemp(screen, LOWER, LOWER->size))
		return(SM_ERROR);
	    while (phyReadTemp(screen, UPPER) == 0)
		if (phyWriteTemp(screen, LOWER, UPPER->currentRecord))
		    return(SM_ERROR);
	    UPPER->size = 0;
	    UPPER->offset = 0;
	    positionTemp(screen, UPPER, (INDEX) 0);
	}

#ifdef TEST
	if (debugLevel > 1)
	    PRINTF0("Swapping UPPER & LOWER\n");
#endif
	temp = UPPER;
	UPPER = LOWER;
	LOWER = temp;
    }

#ifdef TEST
    if (debugLevel > 1)
	PRINTF0("There is now definitely an UPPER with SCREEN_INDEX in it\n");
#endif

    if (SCREEN_INDEX > LOWER->size - LOWER->offset) {
#ifdef TEST
	if (debugLevel > 1)
	    PRINTF0("Now copy records from the start of UPPER until at INDEX\n");
#endif
	if (positionTemp(screen, LOWER, LOWER->size))
	    return(SM_ERROR);
	if (positionTemp(screen, UPPER, UPPER->offset))
	    return(SM_ERROR);
	while ((LOWER->size - LOWER->offset < SCREEN_INDEX) && 
	       (phyReadTemp(screen, UPPER) != SM_ERROR)) {
	    if (phyWriteTemp(screen, LOWER, UPPER->currentRecord))
		return(SM_ERROR);
	    UPPER->offset++;
	}
    }

    positionTemp(screen, LOWER, SCREEN_INDEX + LOWER->offset);
    return(0);
}

static int writeRecordToTemp ARG3(SM_SCREEN, screen, CHAR *, buf, int, overWrite)
{
    SM_TEMP_FILE	targetFile;
    INDEX		targetRecord;

#ifdef TEST
    if (debugLevel > 1)
	PRINTF3("writeRecordToTemp index=%ld, overWrite=%d, buf='%s'\n",
		SCREEN_INDEX,
		overWrite,
		buf);
    if (debugLevel > 2)
	dumpScreenParams(screen);
#endif

    /* Simple & cheap cases first */
    if ((SCREEN_INDEX == NUM_RECORDS) || /* at eof */
	(overWrite && (SCREEN_INDEX + 1 == NUM_RECORDS))) { /* last record */
	if (UPPER->size - UPPER->offset) {
	    targetFile = UPPER;
	    targetRecord = SCREEN_INDEX + UPPER->offset - (LOWER->size - LOWER->offset);
	} else {
	    targetFile = LOWER;
	    targetRecord = SCREEN_INDEX + LOWER->offset;
	}
	if (positionTemp(screen, targetFile, targetRecord))
	    return(SM_ERROR);

    } else {
	if (positionSplit(screen))
	    return(SM_ERROR);
	/* important to do this after split as LOWER & UPPER may have been swapped over */
	targetFile = LOWER;
	if (overWrite && (UPPER->size - UPPER->offset))
	    UPPER->offset++;
    }

    if (phyWriteTemp(screen, targetFile, buf) == 0) {
	if ((SCREEN_INDEX == NUM_RECORDS) || !overWrite)
	    NUM_RECORDS++;
	if (overWrite)
	    SCREEN_INDEX++;
	screen->dirty_file = 1;
	return(0);
    }
    return(SM_ERROR);
}

static int deleteRecord ARG1(SM_SCREEN, screen)
{
    assert(SCREEN_INDEX < NUM_RECORDS);

#ifdef TEST
    if (debugLevel > 1)
	PRINTF1("deleteRecord at %ld\n", SCREEN_INDEX);
    if (debugLevel > 2)
	dumpScreenParams(screen);
#endif

    if (NUM_RECORDS) {
	/* Simple, cheap cases first */
	if (SCREEN_INDEX == 0) {
	    if (LOWER->size - LOWER->offset)
		LOWER->offset++;
	    else
		UPPER->offset++;
	} else if (SCREEN_INDEX == NUM_RECORDS - 1) {
	    /* We're deleting the last record */
	    if (SCREEN_INDEX < LOWER->size - LOWER->offset)
		/* We're in the LOWER FILE */
		LOWER->size--;
	    else
		UPPER->size--;
	} else if (SCREEN_INDEX == LOWER->size - LOWER->offset)
	    /* We're deleting the first record in UPPER */
	    UPPER->offset++;
	else if ((LOWER->size - LOWER->offset) && (SCREEN_INDEX == LOWER->size - LOWER->offset - 1))
	    /* We're deleting the last record in LOWER */
	    LOWER->size--;
	else { /* Not simple ... */
	    if (positionSplit(screen))
		return(SM_ERROR);
	    UPPER->offset++;
	}
	NUM_RECORDS--;
	screen->dirty_file = 1;
    }
    return(0);
}

/* Counts records as they are read in - pass in filename = NULL to simply
   initialise the data structures for temp_storage == 'F' */
int smdb_read_file ARG3(CHAR *, filename, SM_SCREEN, screen, int, untabify)
{
    CHAR	*buf = alloca(SM_MAXREC), *outbuf = alloca(SM_MAXREC);
    CHAR	*pos, *end;
    int		len;
    FILE	*f = NULL;

    if (filename && (f = fopen(filename, "r")) == NULL) {
	sm_file_error("Can't read file: ", filename, errno);
        return(SM_ERROR);
    }

#ifdef TEST
    PRINTF1("Reading '%s'\n", filename);
#else
    sprintf(buf, "Reading <%s>.", filename);
    sm_message(buf);
#endif

    if (screen->temp_storage == 'F') { /* Open temp files */
	if (LOWER == NULL)
	    LOWER = newTempFile();

	sm_mktemp(buf, "LXXXXXX");
	if ((*buf == 0) || ((LOWER->file = fopen(buf, "w+")) == NULL)) {
	    sm_error("Can't open temporary file");
	    if (f)
		fclose(f);
	    deleteTempFile(LOWER);
	    LOWER = NULL;
	    return(SM_ERROR);
	} else
	    SM_ASSIGN(LOWER->fileName, buf);

	if (UPPER == NULL)
	    UPPER = newTempFile();

	sm_mktemp(buf, "UXXXXXX");
	if ((*buf == 0) || ((UPPER->file = fopen(buf, "w+")) == NULL)) {
	    sm_error("Can't open temporary file");
	    deleteTempFile(LOWER);
	    LOWER = NULL;
	    deleteTempFile(UPPER);
	    UPPER = NULL;
	    if (f)
		fclose(f);
	    return(SM_ERROR);
	} else
	    SM_ASSIGN(UPPER->fileName, buf);
    }

    pos = buf;
    *buf = 0;
    while (f && fgets(pos, SM_MAXREC - (pos - buf), f) != NULL) {

	/* Point 'end' at the terminating 0 */
	end = strchr(pos, 0);
	len = end - buf;

        /* Bark at long lines - but try and continue */
        if (len >= SM_MAXREC - 2)
            sm_error("line too long - but trying to continue");

	*(end + 1) = 0; /* extra 0 */

	if (*buf && (*(end - 1) == '\n'))
	    *--end = 0;

/* Don't #if NEWLINE_AND_CR in case MS-DOS file wanders onto Unix ... */
	if (*buf && (*(end - 1) == '\r'))
	    *--end = 0;

	if (*buf && (*(end - 1) == '\\')) {
	    *--end = 0;
	    pos = end;
	} else { /* At last we have a complete record - store it */
	    smdb_remove_funnies(outbuf, buf, untabify);

	    if (screen->temp_storage == 'F') {
		if (phyWriteTemp(screen, LOWER, outbuf))
		    return(SM_ERROR);
	    } else { /* M */
		CHAR	*p = xmalloc((INDEX) (strlen(outbuf) + 1));
		strcpy(p, outbuf);
		screen->data = addToArray(screen->data, p);
	    }
	    pos = buf;
	    *buf = 0;
	    NUM_RECORDS++;
        }
    }
    if (screen->temp_storage == 'F') {
	SCREEN_INDEX = NUM_RECORDS;
	fseek(LOWER->file, ftell(LOWER->file), 0); /* allow read to LOWER */
    }
    screen->dirty_file = 0;
    smdb_seek(screen, (INDEX) 0);
    if (f)
	fclose(f);
    sm_message("");
    return(0);
}

char **build_sort_command ARG1(SM_SCREEN, screen)
{
    char	**argv = NULL, buf[100];
    INDEX	size = MAX_INDEX;
    int		fieldnum, char_start, char_end;
    SM_SORTKEY	*skp;

    assert(screen);

    argv = sm_increase_size(argv, &size);
    SM_ASSIGN(argv[size], "sort");
    if (screen->parent->separator != ' ') {
	argv = sm_increase_size(argv, &size);
	SM_ASSIGN(argv[size], "-T ");
	argv[size][2] = screen->parent->separator;
    }
    for (skp = sm_first_sortkey(screen); 
	 skp && *skp; 
	 skp = sm_next_sortkey(skp)) {
	if (sm_calc_sort_params(screen, 
				(*skp)->sortfield_name, 
				&fieldnum, 
				&char_start,
				&char_end) == 0) {
	    /* start of key */
	    argv = sm_increase_size(argv, &size);
	    if (char_start > 0)
		sprintf(buf, "+%d.%d", fieldnum, char_start);
	    else
		sprintf(buf, "+%d", fieldnum);
	    SM_ASSIGN(argv[size], buf);

	    /* end of key and flags */
	    argv = sm_increase_size(argv, &size);
	    if (char_end > 0)
		sprintf(buf, "-%d.%d", fieldnum, char_end);
	    else
		sprintf(buf, "-%d", fieldnum + 1);
	    if ((*skp)->ignore_case)
		strcat(buf, "f");
	    if ((*skp)->month)
		strcat(buf, "M");
	    if ((*skp)->numeric)
		strcat(buf, "n");
	    if ((*skp)->reverse)
		strcat(buf, "r");
	    SM_ASSIGN(argv[size], buf);
	}
    }
    return(argv);
}

int smdb_write_file ARG3(CHAR *, filename, SM_SCREEN, screen, int, emergency)
{
    FILE	*f = NULL;
    CHAR	*buf=alloca(200), *buffer, error_filename[50];
    SM_SORTKEY	*sortkey;

    assert(screen);

    sprintf(buf, "Saving ...\"%s\"", filename);
#ifdef TEST
    PRINTF0(buf);
#else
    sm_message(buf);
#endif

    if (screen && screen->temp_storage == 'F') {
#ifndef CANT_PIPE
	int	sort_is_running = 0;

    	if (backup_file(filename) < 0) {
	    sprintf(buf, 
"Can't make a backup file for \"%s\". Will still try to save it.", filename);
#ifdef TEST
	    PRINTF0(buf);
#else
  	    sm_error(buf);
#endif
        }

        if ((f = fopen(filename, "w")) == NULL) {
	    sm_file_error("Can't write to file: ", filename, errno);
	    return(SM_ERROR);
        }
    
	sortkey = sm_first_sortkey(screen);
	if (sortkey && *sortkey) {
	    int		fd[2], pid;
	    char	**argv = NULL;

	    sort_is_running = 1;
	    if (pipe(fd) < 0) {
		sm_error(
"Can't run sort (pipe failed) - trying to write unsorted file.");
		sort_is_running = 0;
	    } else {
		argv = build_sort_command(screen);
		sm_mktemp(error_filename, "EXXXXXX");
		if ((pid = vfork()) == -1) {
		    sm_error(
"Can't run sort (fork failed) - trying to write unsorted file.");
		    sort_is_running = 0;
		} else if (pid == 0) { /* CHILD - exec sort */
		    int		i;

		    /* the child - stdin is pipe, stdout is f, 
		       stderr is error_filename */
		    close(fd[1]);
		    close(0);
		    dup(fd[0]);
		    close(fd[0]);
		    close(1);
		    dup(fileno(f));
		    fclose(f);
		    if ((i = open(error_filename, O_WRONLY |O_CREAT |O_TRUNC))) {
			close(2);
			dup(i);
			close(i);
		    }
		    execvp(argv[0], argv);
		    _exit(1);
		} else { /* PARENT - output to pipe */
		    sm_delete_data(argv);
		    close(fd[0]);
		    fclose(f);
		    f = fdopen(fd[1], "w");
		}
	    }
	}
#endif

	smdb_seek(screen, (INDEX) 0);
	while ((buffer = smdb_get_current(screen)) != NULL) {
	    CHECKWRITE(fputs(buffer, f));
	    CHECKWRITE(fputc('\n', f));
	    smdb_read_next(screen);
	}

#ifndef CANT_PIPE
	if (sort_is_running) {
	    int		status;
	    
	    wait(&status);
	    if (status) {
		FILE		*err = fopen(error_filename, "r");

		if (err) {
		    fgets(buf, 80, err);
		    buf[79] = 0;
		    if (strchr(buf, '\n'))
			*strchr(buf, '\n') = 0;
		    sm_message(buf);
		    fclose(err);
		}
		sm_error(
"The file was not written! Try again (no sort - sort keys are deleted)");
		sm_destroy_all_sortkeys(screen);
	    }
	    unlink(error_filename);
	}
#endif
    } else { /* M */
	ARRAY	data = screen->data;
	CHAR	**p;

	if (data) {
	    SM_SORTKEY	*sortkey;

	    sortkey = sm_first_sortkey(screen);
	    if (!emergency && sortkey && *sortkey) {
		strncpy(buf, sm_msg, 99);
		buf[99] = 0;
		sm_message("Sorting ... ");
		smdb_sort_data(data, sm_first_sortkey(screen));
		sm_message(buf);
	    }
	    
	    if (backup_file(filename) < 0) {
	        sprintf(buf, 
"Can't make a backup file for \"%s\". Will still try to save it.", filename);
#ifdef TEST
		PRINTF0(buf);
#else
	        sm_error(buf);
#endif
            }

            if ((f = fopen(filename, "w")) == NULL) {
		sm_file_error("Can't write to file: ", filename, errno);
	            return(SM_ERROR);
                }
    
	    for (p = (CHAR **)firstArrayItemp(data);
		 p && *p;
		 p = (CHAR **)nextArrayItemp(data, (void **)p)) {
	        CHECKWRITE(fputs(*p, f));
		CHECKWRITE(fputc('\n', f));
	    }
	}
    }

    sprintf(buf, "\"%s\" was successfully saved.", filename);
#ifdef TEST
    PRINTF0(buf);
#else
    sm_message(buf);
#endif

    smdb_seek(screen, (INDEX) 0);
    screen->dirty_file = 0;
#ifdef TEST
    if (debugLevel)
	PRINTF2("smdb_write_file: %ld records output to '%s'\n", 
		NUM_RECORDS, filename);
#endif
    fclose(f);
    return(0);
}

CHAR *smdb_read_next ARG1(SM_SCREEN, screen)
{
    assert(screen);

    if (++SCREEN_INDEX > NUM_RECORDS)
	SCREEN_INDEX = NUM_RECORDS;
	
    if (screen->temp_storage == 'F') {
	return(readRecordFromTemp(screen));
    } else { /* M */
	if (DATA && 
	    (SCREEN_INDEX < NUM_RECORDS))
	    return((CHAR *) arrayItem(DATA, SCREEN_INDEX));
	else
	    return(NULL);
    }
}

CHAR *smdb_read_prev ARG1(SM_SCREEN, screen)
{
    assert(screen);

    if (screen->temp_storage == 'F') {
	if (SCREEN_INDEX == 0)
	    return(NULL);
	SCREEN_INDEX--;
	return(readRecordFromTemp(screen));
    } else { /* M */
	if (SCREEN_INDEX && DATA) {
	    SCREEN_INDEX--;
	    return((CHAR *) arrayItem(DATA, SCREEN_INDEX));
	} else
	    return(NULL);
    }
}

CHAR *smdb_get_current ARG1(SM_SCREEN, screen)
{
    assert(screen);

    if (screen->temp_storage == 'F') {
	if (SCREEN_INDEX < NUM_RECORDS)
	    return(readRecordFromTemp(screen));
	else
	    return(NULL);
    } else { /* M */
	CHAR	*p;

	if (DATA && 
	    NUM_RECORDS &&
	    (SCREEN_INDEX < NUM_RECORDS) && 
	    (p = (CHAR *) arrayItem(DATA, SCREEN_INDEX)))
	    return(p);
	else
	    return(NULL);
    }
}

/* index = MAX_INDEX to position to eof */
CHAR *smdb_seek ARG2(SM_SCREEN, screen, INDEX, index)
{
    assert(screen);

    if (index == MAX_INDEX)
	index = NUM_RECORDS;

    if (screen->temp_storage == 'F') {
	if (index >= NUM_RECORDS) {
	    SCREEN_INDEX = NUM_RECORDS;
	    return(NULL);
	}
	SCREEN_INDEX = index;
	return(readRecordFromTemp(screen));
    } else { /* M */
	if (DATA && (index <= NUM_RECORDS)) {
	    SCREEN_INDEX = index;
	    return((CHAR *) arrayItem(DATA, SCREEN_INDEX));
	} else
	    return(NULL);
    }
}

/* Similar to 'write' but does not advance the file pointer. */
int smdb_set_current ARG2(SM_SCREEN, screen, CHAR *, new_record)
{
    assert(screen);

    if (screen->temp_storage == 'F') {
	INDEX	saveIndex = screen->index;

	if (writeRecordToTemp(screen, new_record, 1) == EOF)
	    return(SM_ERROR);
	screen->index = saveIndex;
    } else { /* M */
	CHAR	*p = NULL;

	if (DATA && (SCREEN_INDEX < NUM_RECORDS) && (p = (CHAR *) arrayItem(DATA, SCREEN_INDEX))) {
	    SM_ASSIGN(p, new_record);
	    setItem(DATA, SCREEN_INDEX, p);
	} else {
	    p = NULL;
	    SM_ASSIGN(p, new_record);
	    DATA = addToArray(DATA, p);
	    SCREEN_INDEX = NUM_RECORDS++;
	}
    }
    screen->dirty_file = 1;
    return(0);
}

/* Overwrite record SCREEN_INDEX or append to file if at eof */
int smdb_write ARG2(SM_SCREEN, screen, CHAR *, new_record)
{
    assert(screen);

    if (screen->temp_storage == 'F') {
	if (writeRecordToTemp(screen, new_record, 1))
	    return(SM_ERROR);
    } else { /* M */
	CHAR	*p = NULL;

	if (DATA && (SCREEN_INDEX < NUM_RECORDS) && (p = (CHAR *) arrayItem(DATA, SCREEN_INDEX))) {
	    SM_ASSIGN(p, new_record);
	    setItem(DATA, SCREEN_INDEX, p);
	} else {
	    /* we are at the end - add a record */
	    SM_ASSIGN(p, new_record);
	    DATA = addToArray(DATA, p);
	    assert(arrayLength(DATA));
	    SCREEN_INDEX = arrayLength(DATA) - 1;
	    NUM_RECORDS = arrayLength(DATA);
	}
	SCREEN_INDEX++;
    }
    screen->dirty_file = 1;
    return(0);
}

int smdb_insert_rec ARG2(SM_SCREEN, screen, CHAR *, new_record)
{
    assert(screen);

    if (screen->temp_storage == 'F') {
	if (writeRecordToTemp(screen, new_record, 0) == EOF)
	    return(SM_ERROR);
    } else { /* M */
	CHAR	*p = NULL;

	SM_ASSIGN(p, new_record);
	insertInArray(DATA, p, SCREEN_INDEX);
	NUM_RECORDS++;
    }

    screen->dirty_file = 1;
    return(0);
}

/* Note: deleting a record must move the file pointer to the next record
   so that get_next will get the correct value */
int smdb_delete_rec ARG1(SM_SCREEN, screen)
{
    assert(screen);

    if ((NUM_RECORDS == 0) || (SCREEN_INDEX == NUM_RECORDS))
	return(SM_ERROR);

    assert(SCREEN_INDEX < NUM_RECORDS);

    if (screen->temp_storage == 'F') {
	deleteRecord(screen);
    } else { /* M */
	CHAR	*p;

	if (DATA && (SCREEN_INDEX < NUM_RECORDS) && (p = (CHAR *) arrayItem(DATA, SCREEN_INDEX))) {
	    free(p);
	    removeFromArray(DATA, SCREEN_INDEX);
	    NUM_RECORDS--;
	}
    }	
    screen->dirty_file = 1;
    return(0);
}

void smdb_delete_data ARG1(SM_SCREEN, screen)
{
    assert(screen);

    if (screen->temp_storage == 'F') {
	deleteTempFile(LOWER);
	LOWER = NULL;
	deleteTempFile(UPPER);
	UPPER = NULL;
    } else if (DATA) { /* M */
	f_ptr	f;
	f = (f_ptr) free;
	removeAllItemsFromArray(DATA, f);
	freeArray(DATA);
    }
    NUM_RECORDS = 0;
    screen->dirty_file = 0;
    SCREEN_INDEX = 0;
}

INDEX smdb_num_records ARG1(SM_SCREEN, screen)
{
    assert(screen);

    return(NUM_RECORDS);
}

INDEX smdb_enq_position ARG1(SM_SCREEN, screen)
{
    assert(screen);

    return(SCREEN_INDEX);
}

/* Has the file attached to the screen changed? */
int smdb_file_changed ARG1(SM_SCREEN, screen)
{
    assert(screen);

    return(screen->dirty_file);
}

void smdb_set_file_unchanged ARG1(SM_SCREEN, screen)
{
    assert(screen);

    screen->dirty_file = 0;
}

void smdb_set_file_changed ARG1(SM_SCREEN, screen)
{
    assert(screen);

    screen->dirty_file = 1;
}

struct sort_item_s {
    CHAR		*key;
    CHAR		*text;
    struct sort_item_s	*next;
};
typedef struct sort_item_s *SORT_PTR;

static SM_SORTKEY *primary_sortkey, *this_sortkey;
static int sm_compare ARG2(const void *, d1, const void *, d2)
{
    int 	r;
    INDEX	i, j;

    if ((*this_sortkey)->numeric) {
	i = AsciiToIndex((*(SORT_PTR *)d1)->key);
	j = AsciiToIndex((*(SORT_PTR *)d2)->key);
	r = i < j? -1: i > j? 1: 0;
    } else
	r = strcmp((*(SORT_PTR *)d1)->key, (*(SORT_PTR *)d2)->key);
    if (r) {
	if (((*this_sortkey)->reverse))
	    r = -r;
	this_sortkey = primary_sortkey;
	return(r);
    } else if (sm_next_sortkey(this_sortkey) && *sm_next_sortkey(this_sortkey)) { 
	/* i.e. if there is a secondary key */
	this_sortkey = sm_next_sortkey(this_sortkey);
	r = sm_compare(&((*(SORT_PTR *)d1)->next), &((*(SORT_PTR *)d2)->next));
	this_sortkey = primary_sortkey;
	return(r);
    } else {
	this_sortkey = primary_sortkey;
	return(0);
    }
}

/* qSort an array in memory */
int smdb_sort_data ARG2(ARRAY, data, SM_SORTKEY *, sortkeyp)
{
    INDEX	number;
    int		unchanged;
    SORT_PTR	*sort_item, *sort_vector;
    SM_FIELD	current_field;
    CHAR	**temp, *string;
    SM_SORTKEY	*keyp, key;
    SM_SCREEN	*screenp;

    assert(sortkeyp && *sortkeyp);
    assert(data);

    /* Get the fields corresponding to the sortkey names ... */
    for (keyp = sortkeyp; 
	 keyp && *keyp; 
	 keyp = sm_next_sortkey(keyp)){
	key = *keyp;
	if (key->sortfield == NULL) {
	    screenp = sm_first_scr(sm_user_screens());
	    if ((key->sortfield = 
		 sm_fld_open(screenp? *screenp: NULL, 
			     key->sortfield_name)) == NULL) {
		CHAR *buf = alloca(100);
		
		sprintf(buf, "No such sort field: <%s>", key->sortfield_name);
#ifdef TEST
		PRINTF0(buf);
#else
		sm_message(buf);
#endif
		return(SM_ERROR);
	    }
	}
    }
    
    sm_message("Sorting ...");
    
    number = arrayLength(data);

    /* Build sort keys */
    sort_vector = (SORT_PTR *) alloca(sizeof(SORT_PTR) * (number + 1));
    
    sort_item = sort_vector;
    for (temp = (CHAR **) firstArrayItemp(data);
	 temp && *temp;
	 temp = (CHAR **) nextArrayItemp(data, (void **)temp)) {
	SORT_PTR	item;

	sm_data_to_screen((*sortkeyp)->parent, *temp, 0);
	for (keyp = sortkeyp; 
	     keyp && *keyp; 
	     keyp = sm_next_sortkey(keyp)) { /* For each sort key ... */
	    item = (SORT_PTR) alloca(sizeof(struct sort_item_s));
	    item->key = NULL;
	    item->text = NULL;
	    item->next = NULL;

	    if (keyp == sortkeyp) /* Primary key */
		*sort_item = item;
	    else { /* Secondary keys get linked on the end */
		SORT_PTR sec = *sort_item;

		while (sec->next)
		    sec = sec->next;
		sec->next = item;
	    }

	    current_field = (*keyp)->sortfield;
	    string = sm_fld_enq(current_field);
	    item->key = alloca(strlen(string) + 1);
	    item->text = *temp;
	    strcpy(item->key, string);
	    if ((*keyp)->ignore_case)
		sm_toupper(item->key);
	}
	sort_item++;
    }
    *sort_item = NULL;
    
    this_sortkey = primary_sortkey = sortkeyp;
    qsort((CHAR *) sort_vector, number, sizeof(SORT_PTR), sm_compare);
    
    /* Restablish data in sorted order ... */
    sort_item = sort_vector;
    temp = (CHAR **) firstArrayItemp(data);
    unchanged = 1;
    while (*sort_item) {
	if (*temp != (*sort_item)->text) {
	    *temp = (*sort_item)->text;
	    unchanged = 0;
	}
	temp = (CHAR **) nextArrayItemp(data, (void *) temp);
	sort_item++;
    }
    
    sm_message("");
    return(unchanged);
}

#ifdef TEST

#include <setjmp.h>
#include <signal.h>
#ifdef SM_SYSV
#include <stdlib.h> /* for RAND_INT */
#endif
#ifdef SM_SUNOS
#include <limits.h>
#define RAND_MAX INT_MAX
#endif
#ifdef SM_TURBOC
need RAND_INT
#endif

jmp_buf		NoMoreHeap;

f_ptr sm_get_field_trigger ARG1(SM_FIELD, field)
{
    return(NULL);
}

char *prin ARG2(SM_SCREEN, s, char *, p)
{
    PRINTF2("%d: '%s'\n", smdb_enq_position(s), (p?p:"---NULL---"));
    return(p);
}

usage()
{
    printf("s [-mc -dnum -tFileName -lFileName -vMessage]\n\nTests smdb File storage\n\n");
    printf("-m           :to use memory storage (default is file)\n");
    printf("-c           :to use constant length records & extra checking\n");
    printf("-dnum        :set debug level to num (1-3)\n");
    printf("-tFileName   :to turn on tracing\n");
    printf("-lFileName   :to turn on logging\n");
    printf("-vMessage    :to send Message to trace file\n\n");
    printf("Commands (either case):\n");
    printf("L            :list next 20 records\n");
    printf("number       :seek to number ( > num_records for eof)\n");
    printf("Wrecord      :write record\n");
    printf("Srecord      :set current to record\n");
    printf("G            :get current\n");
    printf("Irecord      :insert record\n");
    printf("R            :read next (default)\n");
    printf("P            :read previous\n");
    printf("D            :delete record\n");
    printf("Q            :quit\n");
    printf("Ofilename    :output to filename (and dup screen to filename.dup)\n");
    printf("Nfilename    :read commands from filename\n");
    printf("+            :more debugging\n");
    printf("-            :less debugging\n");
    printf(".            :print (some) SM_SCREEN values for test screen\n");
    printf("Z            :print (some) SM_SCREEN values for dup screen\n");
    printf("=            :compare test data and dup\n");
    printf("C            :generate random commands and run until error or abort\n");
    printf("?            :this help\n");
}

/* Make 'record' the right size allowing for \n \r etc */
static void trimToLength ARG2 (char *, record, int, length)
{
#ifdef __TURBOC__
    length -= 2;
#else
    length--;
#endif
    if (length) {
	while (strlen(record) < length)
	    strcat(record, " ");
	record[length] = 0;
    }
}

static int my_random ARG1(int, range)
{
    int	max, num;

    range++;
    max = RAND_MAX / range;
    max *= range;
    while ((num = rand()) >= max) continue;
    return(num % range);
}

static int checkAgainstDup ARG2(SM_SCREEN, screen, SM_SCREEN, dup)
{
    INDEX	savedIndex = smdb_enq_position(screen);
    int		errors = 0;

    smdb_seek(screen, (INDEX) 0);
    smdb_seek(dup, (INDEX) 0);
    {
	CHAR	*s, *d;

	while (s = smdb_get_current(screen)) {
	    d = smdb_get_current(dup);
	    if (strcmp(s, d)) {
		PRINTF3("diff at %ld\nscreen='%s'\ndup   ='%s'\n", 
			smdb_enq_position(screen), s, d?d:"--NULL--");
		errors++;
	    }
	    smdb_read_next(screen);
	    smdb_read_next(dup);
	}
	while (d = smdb_get_current(dup)) {
	    PRINTF1("Extra in dup: %s\n", d);
	    smdb_read_next(dup);
	    errors++;
	}
    }
    smdb_seek(screen, savedIndex);
    smdb_seek(dup, savedIndex);
    return(errors);
}

int dumpScreenParams ARG1(SM_SCREEN, screen)
{
    PRINTF1("Screen Index  = %ld\n", smdb_enq_position(screen));
    PRINTF1("Num records   = %ld\n", smdb_num_records(screen));
    PRINTF1("Size of lower = %ld\n", LOWER->size);
    PRINTF1("Lower index   = %ld\n", LOWER->index);
    PRINTF1("Lower offset  = %ld\n", LOWER->offset);
    PRINTF1("Lower ftell() = %ld\n", ftell(LOWER->file));
    PRINTF1("Lower Fname   = %s\n",  LOWER->fileName);
    PRINTF1("Size of upper = %ld\n", UPPER->size);
    PRINTF1("Upper index   = %ld\n", UPPER->index);
    PRINTF1("Upper offset  = %ld\n", UPPER->offset);
    PRINTF1("Upper ftell() = %ld\n", ftell(UPPER->file));
    PRINTF1("Upper Fname   = %s\n",  UPPER->fileName);
    PRINTF1("Dirty file    = %d\n",  screen->dirty_file);
}

void setSignal();

void signalHandler(int signal)
{
    setSignal();
    signalReceived = 1;
}

void setSignal()
{
    signalReceived = 0;
    signal(SIGINT, signalHandler);
}

main ARG2(int, argc, char **, argv)
{
    SM_SCREEN	screen, dup;
    char	command[80], *p, *record, *filename, *boring_commands = "n.?+-z", *Ok = "Ok";
    INDEX	num, count;
    int		repeat_loop = 1;
    int		use_memory = FALSE, c, recordLength = 0, errors = 0, doRandomCommands;
    FILE	*logfile = NULL, *infile = NULL;
    char	*logFileName = NULL, *traceFileName = NULL, *traceMessage = NULL;
    extern char *optarg;

    sm_pre_init(argc, argv);
    sm_init(0, argc, argv); /* startup scrmgr without CURSES */
    smio_term();

    while ((c = getopt(argc, argv, "?cmd:l:t:v:")) != EOF)
        switch (c) {
	case 'c':
	    recordLength = 1;
	    break;
	case 'm':
	    use_memory = TRUE;
	    break;
	case 'd':
	    debugLevel = atoi(optarg);
	    break;
	case 'l':
	    logFileName = optarg;
	    break;
	case 't':
	    traceFileName = optarg;
	    break;
	case 'v':
	    traceMessage = optarg;
	    break;
        default:  
	    usage(); 
	    exit(1);
	    break;
        }
    
    filename = "junk";
    screen = *sm_create_scr(sm_user_screens());
    dup = *sm_create_scr(sm_user_screens());
    if (use_memory)
	screen->temp_storage = 'M';
    else
	screen->temp_storage = 'F';
    if (smdb_read_file(filename, screen, sm_user_screens()->untabify))
	exit(1);

    dup->temp_storage = 'M';
    if (smdb_read_file(filename, dup, sm_user_screens()->untabify))
	exit(1);

    if (setjmp(NoMoreHeap)) {
	printf("Ran out of heap!\n");
	exit(1);
    }

    if (recordLength) {
	recordLength = strlen(smdb_get_current(screen)) + 1; /* for NL */
#if __TURBOC__
	recordLength++; /* for CR */
#endif
    }

    if (logFileName && *logFileName) {
	logfile = fopen(logFileName, "w");
	if (!logfile)
	    fprintf(stderr, "Can't open logfile '%s'...continuing without it.", logFileName);
    }

    if (traceFileName && *traceFileName) {
	trace = fopen(traceFileName, "a");
	if (!trace)
	    fprintf(stderr, "Can't open tracefile '%s'...continuing without it.", traceFileName);
    } else
	trace = stdout;

    if (traceMessage) {
	PRINTF0("message=");
	PRINTF0(traceMessage);
	PRINTF0("\n");
    }

    setSignal();

    while (!feof(stdin) && repeat_loop) {
	/* Check file position if possible */
	if (recordLength) {
	    if (LOWER->size) {
		long	actualPosition = ftell(LOWER->file)/recordLength;

		if (LOWER->index != actualPosition) {
		    PRINTF2("LOWER->index should be %ld, but is %lu\n", 
			    actualPosition,
			    LOWER->index);
		    errors++;
		}
	    }
	    if (UPPER->size) {
		long	actualPosition = ftell(UPPER->file)/recordLength;

		if (UPPER->index != actualPosition) {
		    PRINTF2("UPPER->index should be %ld, but is %lu\n", 
			    actualPosition,
			    UPPER->index);
		    errors++;
		}
	    }
	}

	if (screen->index != dup->index) {
	    PRINTF2("SCREEN_INDEX is %ld but should be %ld\n", screen->index, dup->index);
	    errors++;
	}

	if (screen->num_records != dup->num_records) {
	    PRINTF2("NUM_RECORDS is %ld but should be %ld\n", screen->num_records, dup->num_records);
	    errors++;
	}

	if (signalReceived) {
	    doRandomCommands = 0;
	    signalReceived = 0;
	}

	/* Get next command */
	command[0] = 0;
	record = &command[1];
	if (doRandomCommands) {
	    /* Generate random commands */
	    char	*commands = "ldswip";
	    int		num;

	    errors += checkAgainstDup(screen, dup);

	    num = my_random(strlen(commands) - 1);
	    command[0] = commands[num];
	    command[1] = 0;

	    if (errors)
		*command = 'q';
	    else if (*command == 'p') {
		num = my_random(NUM_RECORDS);
		sprintf(command, "%d", num);
	    } else if (strchr("wsi", *command)) {
		num = my_random(60);
		if (num)
		    command[num--] = 0;
		while (num)
		    command[num--] = 'a' + my_random(25);
	    }
	} else if (infile) {
	    fgets(command, 80, infile);
	    if (p = strchr(command, '\n'))
		*p = 0;
	    if (feof(infile)) {
		fclose(infile);
		infile = NULL;
	    }
	} else {
	    if (isatty(0)) {
		printf("Command (? for help) [%d]: ", smdb_enq_position(screen));
		fflush(stdout);
	    }
	    gets(command);
	}

	/* Log command if necessary */
	if (logfile && (strchr(boring_commands, tolower(command[0])) == NULL)) {
	    fputs(command, logfile);
	    fputc('\n', logfile);
	    fflush(logfile); /* in case we bomb */
	}
	if (trace && (trace != stdout)) {
	    fprintf(trace, "[%d] command='%s'\n", smdb_enq_position(screen), command);
	} 
	if (!isatty(0) || doRandomCommands)
	    printf("[%d] command='%s'\n", smdb_enq_position(screen), command);

	/* Execute the command */
	switch (tolower(command[0])) {
	case 'l':
	    num = 20;
	    while (num-- && prin(screen, smdb_get_current(screen))) {
		smdb_read_next(screen);
		smdb_read_next(dup);
	    }
	    break;
	case '0':
	case '1':
	case '2':
	case '3':
	case '4':
	case '5':
	case '6':
	case '7':
	case '8':
	case '9':
	    num = AsciiToIndex(command);
	    if (num > smdb_num_records(screen))
		num = MAX_INDEX;
	    p = smdb_seek(screen, num);
	    if (debugLevel)
		prin(screen, p);
	    smdb_seek(dup, num);
	    break;
	case 'w':
	    if (recordLength)
		trimToLength(record, recordLength);

	    if (smdb_write(screen, record) || smdb_write(dup, record)) {
		PRINTF0("smdb_write failed\n");
		errors++;
	    } else if (debugLevel)
		PRINTF1("Wrote: %s\n", record);
	    break;
	case 's':
	    if (recordLength)
		trimToLength(record, recordLength);

	    if (smdb_set_current(screen, record) || smdb_set_current(dup, record)) {
		PRINTF0("smdb_set_current failed\n");
		errors++;
	    } else if (debugLevel)
		PRINTF1("Current record set to '%s'\n", record);
	    break;
	case 'g':
	    p = smdb_get_current(screen);
	    if (debugLevel) {
		PRINTF0("Result of get: ");
		prin(screen, p);
	    }
	    break;
	case 'i':
	    if (recordLength)
		trimToLength(record, recordLength);

	    if (smdb_insert_rec(screen, record) || smdb_insert_rec(dup, record)) {
		PRINTF0("smdb_insert_rec failed\n");
		errors++;
	    } else if (debugLevel)
		PRINTF1("Inserted '%s'\n", record);
	    break;
	case 'd':
	    if (smdb_delete_rec(screen) || smdb_delete_rec(dup)) {
		if (SCREEN_INDEX < NUM_RECORDS) {
		    PRINTF0("smdb_delete_rec failed\n");
		    errors++;
		}
	    } else if (debugLevel)
		PRINTF0("Record deleted\n");
	    break;
	case 'r':
	case 0:
	    p = smdb_read_next(screen);
	    smdb_read_next(dup);
	    if (debugLevel)
		prin(screen, p);
	    break;
	case 'p':
	    p = smdb_read_prev(screen);
	    if (debugLevel)
		prin(screen, p);
	    smdb_read_prev(dup);
	    break;
	case 'q':
	    repeat_loop = 0;
	    break;
	case 'o':
	    if (*record) {
		smdb_write_file(record, screen, 0);
		strcat(command, ".dup");
		if (smdb_write_file(record, dup, 0)) {
		    PRINTF0("Write failed\n");
		    errors++;
		} else if (debugLevel)
		    PRINTF2("Data written to '%s' (& '%s.dup')\n", record, record);
	    }
	    break;
	case '+':
	    debugLevel++;
	    break;
	case '-':
	    if (debugLevel)
		debugLevel--;
	    break;
	case 'n':
	    if (infile) {
		PRINTF0("Already inputting commands - keep it simple, stupid!");
	    } else if (*record)
		if ((infile = fopen(record, "r")) == NULL)
		    PRINTF1("Can't open '%s'\n", record);
	    break;
	case 'z':
	    PRINTF1("Position      = %ld\n", smdb_enq_position(dup));
	    PRINTF1("Num records   = %ld\n", smdb_num_records(dup));
	    PRINTF1("Dirty file    = %d\n",  dup->dirty_file);
	    PRINTF1("debugLevel    = %d\n",  debugLevel);
	    break;
	case '=':
	    errors += checkAgainstDup(screen, dup);
	    break;
	case '.':
	    dumpScreenParams(screen);
	    PRINTF1("debugLevel    = %d\n",  debugLevel);
	    if (recordLength)
		PRINTF1("Record length = %d\n", recordLength);
	    break;
	case 'c':
	    /*srand(time(NULL)); ... not a good idea if you want to recreate a bug!*/
	    doRandomCommands = 1;
	    break;
	case '?':
	    usage();
            break;
	default:
	    printf("Huh?\n");
	    break;
	}
    }
    smdb_delete_data(screen);
    smdb_delete_data(dup);
    if (logfile)
	fclose(logfile);
    PRINTF1("exiting status = %d\n", errors);
    if (trace != stdout)
	fclose(trace);

    exit(errors);
}

#endif

/* For emacs:
 * Local Variables:
 * tab-width: 8
 * End:
 */
